package com.demo.security.access.intercept;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import com.demo.security.access.AccessLogicManager;
import com.demo.security.access.AfterLogicManager;
import com.demo.security.access.LogicAttribute;
import com.demo.security.access.LogicMetadataSource;
import com.demo.security.core.SecurityMessageSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;

/**
 * 
 * 抽象逻辑拦截器 各种拦截器处理模板
 * 
 * @author ryuu.kk
 */
public abstract class AbstractLogicInterceptor implements InitializingBean,	ApplicationEventPublisherAware, MessageSourceAware {

	/**
	 * Log日志类
	 */
	protected final Log logger = LogFactory.getLog(getClass());
	/**
	 * 消息资源
	 */
	protected MessageSourceAccessor messages = SecurityMessageSource.getAccessor();
	
	/**
	 * 事件Publisher
	 */
	protected ApplicationEventPublisher eventPublisher;
	/**
	 * 访问逻辑管理器
	 */
	private AccessLogicManager accessLogicManager;
	/**
	 * 访问后处理管理器
	 */
    private AfterLogicManager afterInvocationManager;
    /**
     * 验证逻辑配置属性
     */
    private boolean validateLogicAttributes = true;
    
    /**
     * 取得逻辑对象类
     * @return 逻辑对象类
     */
    public abstract Class<?> getLogicObjectClass();
    
    /**
     * 获得方法调用拦截所需要的元数据资源
     * @return LogicMetadataSource
     */
    public abstract LogicMetadataSource obtainLogicMetadataSource();
    
	/**
	 * 初始化处理
	 */
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(getLogicObjectClass(), "Subclass must provide a non-null response to getSecureObjectClass()");
		Assert.notNull(this.messages, "A message source must be set");
		Assert.notNull(this.accessLogicManager, "An AccessLogicManager is required");
		if (afterInvocationManager != null) {
			 Assert.isTrue(this.afterInvocationManager.supports(getLogicObjectClass()),
	                    "AfterLogicManager does not support secure object class: " + getLogicObjectClass());
		}
        if (this.validateLogicAttributes) {
            Collection<LogicAttribute> attributeDefs = this.obtainLogicMetadataSource().getAllAttributes();

            if (attributeDefs == null) {
                logger.warn("Could not validate logic configuration attributes as the LogicMetadataSource did not return "
                        + "any attributes from getAllAttributes()");
                return;
            }

            Set<LogicAttribute> unsupportedAttrs = new HashSet<LogicAttribute>();

            for (LogicAttribute attr : attributeDefs) {
                if (!this.accessLogicManager.supports(attr)
                        && ((this.afterInvocationManager == null) || !this.afterInvocationManager.supports(attr))) {
                    unsupportedAttrs.add(attr);
                }
            }

            if (unsupportedAttrs.size() != 0) {
                throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttrs);
            }

            logger.debug("Validated configuration attributes");
        }
	}
	
	protected InterceptorLogicToken beforeInvocation(Object object) {

		Assert.notNull(object, "Object was null");
		final boolean debug = logger.isDebugEnabled();

		if (!getLogicObjectClass().isAssignableFrom(object.getClass())) {
			throw new IllegalArgumentException(
					"Logic invocation attempted for object "
							+ object.getClass().getName()
							+ " but AbstractLogicInterceptor only configured to support logic objects of type: "
							+ getLogicObjectClass());
		}

		Collection<LogicAttribute> attributes = this.obtainLogicMetadataSource().getAttributes(object);
		
		if (attributes == null || attributes.isEmpty()) {
			logger.warn("Logic attribute is empty in method " + object.toString());
			//publishEvent(new PublicLogicInvocationEvent(object));
			return null;
		}
        if (debug) {
            logger.debug("Logic object: " + object + "; Attributes: " + attributes);
        }
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
        	logger.warn(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"));
            return null;
        }

        Authentication authenticated = authenticateIfRequired();
        
        this.accessLogicManager.access(authenticated, object, attributes);
        
        if (debug) {
        	logger.debug("access logic is finished.");
        }
        
        return new InterceptorLogicToken(SecurityContextHolder.getContext(), attributes, object);
	}
	
	protected Object afterInvocation(InterceptorLogicToken token, Object returnedObject) {
        if (token == null) {
            // public object
            return returnedObject;
        }
        
		if (afterInvocationManager != null) {
			returnedObject = afterInvocationManager.access(token
					.getSecurityContext().getAuthentication(), token
					.getLogicObject(), token.getAttr(), returnedObject);
		}

        return returnedObject;
	}
	
    /**
     * Checks the current authentication token and passes it to the AuthenticationManager if
     * {@link org.springframework.security.core.Authentication#isAuthenticated()} returns false or the property
     * <tt>alwaysReauthenticate</tt> has been set to true.
     *
     * @return an authenticated <tt>Authentication</tt> object.
     */
    private Authentication authenticateIfRequired() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication.isAuthenticated()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Previously Authenticated: " + authentication);
            }
            return authentication;
        }
        return null;
    }
    
	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	public void setApplicationEventPublisher(
			ApplicationEventPublisher applicationEventPublisher) {
		this.eventPublisher = applicationEventPublisher;
	}

	public void setAccessLogicManager(AccessLogicManager accessLogicManager) {
		this.accessLogicManager = accessLogicManager;
	}

	public void setAfterInvocationManager(AfterLogicManager afterInvocationManager) {
		this.afterInvocationManager = afterInvocationManager;
	}

	private void publishEvent(ApplicationEvent event) {
		if (this.eventPublisher != null) {
			this.eventPublisher.publishEvent(event);
		}
	}
}
